package ${packageName};

import com.google.protobuf.ByteString;
import org.tron.tronj.abi.FunctionReturnDecoder;
import org.tron.tronj.abi.TypeReference;
import org.tron.tronj.abi.datatypes.Address;
import org.tron.tronj.abi.datatypes.Bool;
import org.tron.tronj.abi.datatypes.Function;
import org.tron.tronj.abi.datatypes.Uint;
import org.tron.tronj.abi.datatypes.Utf8String;
import org.tron.tronj.abi.datatypes.generated.Uint256;
import org.tron.tronj.abi.datatypes.generated.Uint8;
import org.tron.tronj.client.TronClient;
import org.tron.tronj.client.transaction.TransactionBuilder;
import org.tron.tronj.proto.Chain;
import org.tron.tronj.proto.Response;
import org.tron.tronj.utils.Base58Check;
import org.tron.tronj.utils.Numeric;
import top.rhynie.web3j.Web3J;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ${className} {

    private TronClient client;

    private ByteString ownerAddress;

    private ByteString contractAddress;

    private long feeLimit;

    private ${className} (Web3J web3j, String ownerAddress, String contractAddress, long feeLimit) {
        this.client = web3j.getClient();
        this.ownerAddress = TronClient.parseAddress(ownerAddress);
        this.contractAddress = TronClient.parseAddress(contractAddress);
        this.feeLimit = feeLimit;
    }

    public static ${className} load(Web3J web3j, String ownerAddress, String contractAddress, long feeLimit) {
        return new ${className}(web3j, ownerAddress, contractAddress, feeLimit);
    }

<#list functions as function >
   public <#if function.constant = true>List<Object><#else>boolean</#if> ${function.name} (
    <#list function.inputs! as input>
        <#if input.type = "address">
            String <#if input.name??>${"param" + input_index} <#else>${input.name}</#if><#if input_index != function.inputs?size - 1>,</#if>
            <#elseif input.type = "string">
                String <#if input.name??>${"param" + input_index} <#else>${input.name}</#if><#if input_index != function.inputs?size - 1>,</#if>
            <#elseif input.type = "uint256">
            BigInteger <#if input.name??>${"param" + input_index} <#else>${input.name}</#if><#if input_index != function.inputs?size - 1>,</#if>
        </#if>
    </#list>
    ) {
        Function function = new Function("${function.name}",
            <#if function.inputs?size != 0>
                Arrays.asList(
                <#list function.inputs! as input>
                    <#if input.type = "address">
                        new Address(<#if input.name??>${"param" + input_index} <#else>${input.name}</#if>)<#if input_index != function.inputs?size - 1>,</#if>
                    <#elseif input.type = "string">
                        new Utf8String(<#if input.name??>${"param" + input_index} <#else>${input.name}</#if>)<#if input_index != function.inputs?size - 1>,</#if>
                    <#elseif input.type = "uint256">
                        new Uint256(<#if input.name??>${"param" + input_index} <#else>${input.name}</#if>)<#if input_index != function.inputs?size - 1>,</#if>
                    <#elseif input.type = "bool">
                        new Bool(<#if input.name??>${"param" + input_index} <#else>${input.name}</#if>)<#if input_index != function.inputs?size - 1>,</#if>
                    </#if>
                </#list>
                ),
                <#else>
                    Collections.emptyList(),
            </#if>
            <#if function.outputs?size != 0>
                Arrays.asList(
                    <#list function.outputs! as output>
                    <#if output.type = "address">
                    new TypeReference<Address>() {}<#if output_index != function.outputs?size - 1>,</#if>
                    <#elseif output.type = "string">
                    new TypeReference<Utf8String>() {}<#if output_index != function.outputs?size - 1>,</#if>
                    <#elseif output.type = "uint256">
                    new TypeReference<Uint>() {}<#if output_index != function.outputs?size - 1>,</#if>
                    <#elseif output.type = "uint8">
                    new TypeReference<Uint8>() {}<#if output_index != function.outputs?size - 1>,</#if>
                    <#elseif output.type = "bool">
                    new TypeReference<Bool>() {}<#if output_index != function.outputs?size - 1>,</#if>
                    </#if>
                    </#list>
                )
                <#else>
                    Collections.emptyList()
            </#if>

        );
        <#if function.constant = true>
            Response.TransactionExtention txnExt = client.constantCall(Base58Check.bytesToBase58(ownerAddress.toByteArray()), Base58Check.bytesToBase58(contractAddress.toByteArray()), function);
            List<ByteString> constantResultList = txnExt.getConstantResultList();
            String result = Numeric.toHexString(constantResultList.get(0).toByteArray());
            List<Object> returnValues = Stream.iterate(0, x -> ++x)
                .limit(function.getOutputParameters().size())
                .map(x -> FunctionReturnDecoder.decode(result, function.getOutputParameters()).get(x).getValue())
                .collect(Collectors.toList());
            return returnValues;
        <#else>
            TransactionBuilder builder = client.triggerCall(Base58Check.bytesToBase58(ownerAddress.toByteArray()),
            Base58Check.bytesToBase58(contractAddress.toByteArray()), function);
            builder.setFeeLimit(feeLimit);
            Chain.Transaction signedTxn = client.signTransaction(builder.build());
            Response.TransactionReturn ret = client.broadcastTransaction(signedTxn);
            return ret.getResult();
        </#if>
   }

</#list>

}